﻿using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Reflection;
using OpenLS.Core.Serialization;
using OpenLS.Spreadsheet.Expressions;

namespace OpenLS.Spreadsheet
{
    public abstract class TableBase: IOfficeXmlSerializable, INotifyPropertyChanged
    {
        private RangeReference _rangeRef;
        protected bool inSetup;

        public Range Range
        {
            get
            {
                if (_rangeRef == null)
                    return null;
                return _rangeRef.Range;
            }
            set
            {
                if (Range != null)
                    UnhookEvents();
                _rangeRef = value.ToReference();
                HookEvents();
                notifyPropertyChanged("Range");
            }
        }

        void IOfficeXmlSerializable.ReadXml(ReadContext context)
        {
            this.ReadXml(context);
        }

        internal abstract void ReadXml(ReadContext context);
        internal abstract void WriteXml(WriteContext context);

        void IOfficeXmlSerializable.WriteXml(WriteContext context)
        {
            WriteXml(context);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        internal void HookEvents()
        {
#if DEBUG
            inSetup = true;
#endif
            foreach (Cell cell in Range.GetNonEmptyCells())
            {
                cell.TableBase = this;
            }
            Range.Worksheet.CellAdded += WorksheetCellAdded;
            Range.Worksheet.CellMoved += WorksheetCellMoved;
            hookRowColumns();
#if DEBUG
            inSetup = false;
#endif
        }

        protected abstract void hookRowColumns();
        protected abstract void unhookRowColumns();

        internal void WorksheetCellMoved(object sender, CellMovedArgs e)
        {
            bool hasOrigin = Range.Contains(e.Origin);
            bool hasDestination = Range.Contains(e.Cell.Address);
            if (hasOrigin == hasDestination)
                return;
            if (hasOrigin && e.Cell.Table == this)
                e.Cell.Table = null;
            if (hasDestination)
                e.Cell.TableBase = this;
        }

        protected void notifyPropertyChanged(string propertyName)
        {
            Contract.Requires(GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null);
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        internal void UnhookEvents()
        {
#if DEBUG
            inSetup = true;
#endif
            foreach (Cell cell in Range.GetNonEmptyCells())
                cell.TableBase = null;
            Range.Worksheet.CellAdded -= WorksheetCellAdded;
            Range.Worksheet.CellMoved -= WorksheetCellMoved;
            unhookRowColumns();
#if DEBUG
            inSetup = false;
#endif
        }

        internal void WorksheetCellAdded(object sender, CellEventArgs e)
        {
            if (Range.Contains(e.Cell))
                e.Cell.TableBase = this;
        }
    }
}